ace.define(
  'ace/ext/elastic_tabstops_lite',
  ['require', 'exports', 'module', 'ace/editor', 'ace/config'],
  function (require, exports, module) {
    'use strict';
    var ElasticTabstopsLite = function (editor) {
      this.$editor = editor;
      var self = this;
      var changedRows = [];
      var recordChanges = false;
      this.onAfterExec = function () {
        recordChanges = false;
        self.processRows(changedRows);
        changedRows = [];
      };
      this.onExec = function () {
        recordChanges = true;
      };
      this.onChange = function (delta) {
        if (recordChanges) {
          if (changedRows.indexOf(delta.start.row) == -1) changedRows.push(delta.start.row);
          if (delta.end.row != delta.start.row) changedRows.push(delta.end.row);
        }
      };
    };
    (function () {
      this.processRows = function (rows) {
        this.$inChange = true;
        var checkedRows = [];
        for (var r = 0, rowCount = rows.length; r < rowCount; r++) {
          var row = rows[r];
          if (checkedRows.indexOf(row) > -1) continue;
          var cellWidthObj = this.$findCellWidthsForBlock(row);
          var cellWidths = this.$setBlockCellWidthsToMax(cellWidthObj.cellWidths);
          var rowIndex = cellWidthObj.firstRow;
          for (var w = 0, l = cellWidths.length; w < l; w++) {
            var widths = cellWidths[w];
            checkedRows.push(rowIndex);
            this.$adjustRow(rowIndex, widths);
            rowIndex++;
          }
        }
        this.$inChange = false;
      };
      this.$findCellWidthsForBlock = function (row) {
        var cellWidths = [],
          widths;
        var rowIter = row;
        while (rowIter >= 0) {
          widths = this.$cellWidthsForRow(rowIter);
          if (widths.length == 0) break;
          cellWidths.unshift(widths);
          rowIter--;
        }
        var firstRow = rowIter + 1;
        rowIter = row;
        var numRows = this.$editor.session.getLength();
        while (rowIter < numRows - 1) {
          rowIter++;
          widths = this.$cellWidthsForRow(rowIter);
          if (widths.length == 0) break;
          cellWidths.push(widths);
        }
        return { cellWidths: cellWidths, firstRow: firstRow };
      };
      this.$cellWidthsForRow = function (row) {
        var selectionColumns = this.$selectionColumnsForRow(row);
        var tabs = [-1].concat(this.$tabsForRow(row));
        var widths = tabs
          .map(function (el) {
            return 0;
          })
          .slice(1);
        var line = this.$editor.session.getLine(row);
        for (var i = 0, len = tabs.length - 1; i < len; i++) {
          var leftEdge = tabs[i] + 1;
          var rightEdge = tabs[i + 1];
          var rightmostSelection = this.$rightmostSelectionInCell(selectionColumns, rightEdge);
          var cell = line.substring(leftEdge, rightEdge);
          widths[i] = Math.max(cell.replace(/\s+$/g, '').length, rightmostSelection - leftEdge);
        }
        return widths;
      };
      this.$selectionColumnsForRow = function (row) {
        var selections = [],
          cursor = this.$editor.getCursorPosition();
        if (this.$editor.session.getSelection().isEmpty()) {
          if (row == cursor.row) selections.push(cursor.column);
        }
        return selections;
      };
      this.$setBlockCellWidthsToMax = function (cellWidths) {
        var startingNewBlock = true,
          blockStartRow,
          blockEndRow,
          maxWidth;
        var columnInfo = this.$izip_longest(cellWidths);
        for (var c = 0, l = columnInfo.length; c < l; c++) {
          var column = columnInfo[c];
          if (!column.push) {
            console.error(column);
            continue;
          }
          column.push(NaN);
          for (var r = 0, s = column.length; r < s; r++) {
            var width = column[r];
            if (startingNewBlock) {
              blockStartRow = r;
              maxWidth = 0;
              startingNewBlock = false;
            }
            if (isNaN(width)) {
              blockEndRow = r;
              for (var j = blockStartRow; j < blockEndRow; j++) {
                cellWidths[j][c] = maxWidth;
              }
              startingNewBlock = true;
            }
            maxWidth = Math.max(maxWidth, width);
          }
        }
        return cellWidths;
      };
      this.$rightmostSelectionInCell = function (selectionColumns, cellRightEdge) {
        var rightmost = 0;
        if (selectionColumns.length) {
          var lengths = [];
          for (var s = 0, length = selectionColumns.length; s < length; s++) {
            if (selectionColumns[s] <= cellRightEdge) lengths.push(s);
            else lengths.push(0);
          }
          rightmost = Math.max.apply(Math, lengths);
        }
        return rightmost;
      };
      this.$tabsForRow = function (row) {
        var rowTabs = [],
          line = this.$editor.session.getLine(row),
          re = /\t/g,
          match;
        while ((match = re.exec(line)) != null) {
          rowTabs.push(match.index);
        }
        return rowTabs;
      };
      this.$adjustRow = function (row, widths) {
        var rowTabs = this.$tabsForRow(row);
        if (rowTabs.length == 0) return;
        var bias = 0,
          location = -1;
        var expandedSet = this.$izip(widths, rowTabs);
        for (var i = 0, l = expandedSet.length; i < l; i++) {
          var w = expandedSet[i][0],
            it = expandedSet[i][1];
          location += 1 + w;
          it += bias;
          var difference = location - it;
          if (difference == 0) continue;
          var partialLine = this.$editor.session.getLine(row).substr(0, it);
          var strippedPartialLine = partialLine.replace(/\s*$/g, '');
          var ispaces = partialLine.length - strippedPartialLine.length;
          if (difference > 0) {
            this.$editor.session
              .getDocument()
              .insertInLine({ row: row, column: it + 1 }, Array(difference + 1).join(' ') + '\t');
            this.$editor.session.getDocument().removeInLine(row, it, it + 1);
            bias += difference;
          }
          if (difference < 0 && ispaces >= -difference) {
            this.$editor.session.getDocument().removeInLine(row, it + difference, it);
            bias += difference;
          }
        }
      };
      this.$izip_longest = function (iterables) {
        if (!iterables[0]) return [];
        var longest = iterables[0].length;
        var iterablesLength = iterables.length;
        for (var i = 1; i < iterablesLength; i++) {
          var iLength = iterables[i].length;
          if (iLength > longest) longest = iLength;
        }
        var expandedSet = [];
        for (var l = 0; l < longest; l++) {
          var set = [];
          for (var i = 0; i < iterablesLength; i++) {
            if (iterables[i][l] === '') set.push(NaN);
            else set.push(iterables[i][l]);
          }
          expandedSet.push(set);
        }
        return expandedSet;
      };
      this.$izip = function (widths, tabs) {
        var size = widths.length >= tabs.length ? tabs.length : widths.length;
        var expandedSet = [];
        for (var i = 0; i < size; i++) {
          var set = [widths[i], tabs[i]];
          expandedSet.push(set);
        }
        return expandedSet;
      };
    }.call(ElasticTabstopsLite.prototype));
    exports.ElasticTabstopsLite = ElasticTabstopsLite;
    var Editor = require('../editor').Editor;
    require('../config').defineOptions(Editor.prototype, 'editor', {
      useElasticTabstops: {
        set: function (val) {
          if (val) {
            if (!this.elasticTabstops) this.elasticTabstops = new ElasticTabstopsLite(this);
            this.commands.on('afterExec', this.elasticTabstops.onAfterExec);
            this.commands.on('exec', this.elasticTabstops.onExec);
            this.on('change', this.elasticTabstops.onChange);
          } else if (this.elasticTabstops) {
            this.commands.removeListener('afterExec', this.elasticTabstops.onAfterExec);
            this.commands.removeListener('exec', this.elasticTabstops.onExec);
            this.removeListener('change', this.elasticTabstops.onChange);
          }
        },
      },
    });
  },
);
(function () {
  ace.require(['ace/ext/elastic_tabstops_lite'], function (m) {
    if (typeof module == 'object' && typeof exports == 'object' && module) {
      module.exports = m;
    }
  });
})();
